package mt;

import java.time.Period;
import java.util.*;

/**
 * @author zhangmin
 * @create 2022-03-17 19:25
 *  小团是美团外卖的区域配送负责人，众所周知，外卖小哥一般都会同时配送若干单，小团在接单时希望把同一个小区的单子放在一起，然后由一名骑手统一配送。
 *  但是由于订单是叠在一起的，所以，他归类订单时只能知道新订单和已有的某个订单的小区是相同的，他觉得这样太麻烦了，所以希望你帮他写一个程序解决这个问题。
 *  即给出若干个形如a b的关系，表示a号订单和b号订单是同一个小区的 ，请你把同一个小区的订单按照编号顺序排序，并分行输出，优先输出最小的订单编号较小的小区订单集合。
 *  订单的编号是1到n。(可能存在同时出现a b和b a这样的关系,也有可能出现a a这样的关系)
 *
 *  并查集：将每个订单看做一个节点
 */
public class meituan2021_2_1 {

    public static void main(String[] args){
        Scanner cin=new Scanner(System.in);
        int n=cin.nextInt();
        int m=cin.nextInt();
        UF uf=new UF(n+1);
        for(int i=0;i< m; i++){
            int a=cin.nextInt();
            int b=cin.nextInt();
            uf.union(a,b);
        }
        System.out.println(uf.count-1);
        boolean[] visited=new boolean[n+1];
        ArrayList<ArrayList<Integer>> lists=new ArrayList<>();
        for (int i = 1; i <= n; i++) {
            if(!visited[i]){
                visited[i]=true;
                ArrayList<Integer> list=new ArrayList<>();
                list.add(i);
                for (int j = i; j <= n; j++) {
                    if(!visited[j]&&uf.connected(i,j)){
                        list.add(j);
                        visited[j]=true;
                    }
                }
                lists.add(list);
            }
        }
        for (int i = 0; i < lists.size(); i++) {
            ArrayList<Integer> list = lists.get(i);
            for (int j = 0; j < list.size(); j++) {
                System.out.print(list.get(j)+" ");
            }
            System.out.print("\n");
        }
    }
    static class UF{
        //连通分量的个数
        private int count;
        //节点x的根节点是parent[x]
        private int[] parent;

        public UF(int count){
            //一开始所有的节点都是不连通的
            this.count=count;
            parent=new int[count];
            for (int i = 0; i < count; i++) {
                parent[i]=i;
            }
        }
        //使得p和q联通
        public void union(int p,int q){
            int rootP=find(p);
            int rootQ=find(q);
            if (rootP==rootQ) return;
            //将小树接在大数下面，较平衡
            parent[rootQ]=rootP;
            count--;
        }
        //找到某个节点x的根节点
        private int find(int x){
            while (parent[x]!=x){
                //压缩路径
                parent[x]=parent[parent[x]];
                x=parent[x];
            }
            return x;
        }
        //判断p和q是否连通
        private boolean connected(int p,int q){
            int rootP=find(p);
            int rootQ=find(q);
            return rootP==rootQ;
        }

    }

}
